package com.pricetag.iqengine;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.pricetag.helpers.Constants;

public class IQEngineAdapter extends Handler {

	private IOnResultCallback onResultCallback = null;
	private Activity activity = null;
	private String deviceId = null;
	private IQRemote iqRemote = null;
	private File imgFile = null;
	private AtomicBoolean iqeRunning = new AtomicBoolean(false);
	private AtomicBoolean stopScanSearch = new AtomicBoolean(false);
	private Map<String, IOnResultCallback> remoteQueryMap = Collections
			.synchronizedMap(new HashMap<String, IOnResultCallback>());
	private int secondCompteur = 0;
	private IQEngineAdapter.RemoteResultUpdateThread updateThread = null;
	private AtomicInteger updateThreadTimer = new AtomicInteger(Constants.IQE_UPDATE_LIMIT);

	private Object newIncomingRemoteMatchSemaphore = new Object();

	public static final int scan = 1;
	public static final int snap = 2;
	public static final int remote = 3;
	public static final String NO_MATCH_FOUND_STR = "no match found";
	public static final int CMD_DECODE = 1;
	public static final int CMD_DECODE_LOCAL = 2;
	public static final int CMD_DECODE_BARCODE = 3;
	public static final int CMD_DECODE_REMOTE = 4;
	public static final int CMD_SUCCESS_BARCODE = 5;
	public static final int CMD_SUCCESS_LOCAL = 6;
	public static final int CMD_SUCCESS_REMOTE = 7;
	public static final int CMD_RESULT = 8;
	public static final int CMD_NO_RESULT = 9;
	public static final int CMD_COMPRESS_IMG = 10;
	public static final int CMD_SERVER_RECEIVED = 11;
	public static final int CMD_NO_RESULT_BARCODE = 12;
	public static final int CMD_NO_RESULT_LOCAL = 13;

	public IQEngineAdapter(Activity activity, IOnResultCallback onResultCallback) {

		String remoteKey = Constants.REMOTE_KEY;
		String remoteSecret = Constants.REMOTE_SECRET;
		this.onResultCallback = onResultCallback;
		this.activity = activity;
		this.deviceId = Utils.getDeviceId(activity);

		initIqSdk(remoteKey, remoteSecret);
	}

	private void initIqSdk(String remoteKey, String remoteSecret) {
		iqRemote = new IQRemote(remoteKey, remoteSecret);
	}

	/**
	 * Method is to be called when user wants to resume update-thread. Usually
	 * called from Activity.onResume.
	 */
	public void start() {
		iqeRunning.set(true);
		this.updateThreadTimer.set(Constants.IQE_UPDATE_LIMIT);
		if (updateThread == null || !updateThread.isAlive()) {
			updateThread = new RemoteResultUpdateThread(this, "RemoteResultUpdateThread");
			updateThread.start();
		}
	}

	/**
	 * Method is to be called when user wants to stop update-thread. Usually
	 * called from Activity.onPause.
	 */
	public void pause() {
		iqeRunning.set(false);
		synchronized (newIncomingRemoteMatchSemaphore) {
			newIncomingRemoteMatchSemaphore.notifyAll();
		}
	}

	public void stop() {
		iqeRunning.set(false);
		removeAllMessages();
		this.iqRemote = null;
	}

	public void removeAllMessages() {
		removeMessages(CMD_DECODE, null);
		removeMessages(CMD_DECODE_BARCODE, null);
		removeMessages(CMD_DECODE_LOCAL, null);
		removeMessages(CMD_DECODE_REMOTE, null);
		removeMessages(CMD_SUCCESS_BARCODE, null);
		removeMessages(CMD_SUCCESS_LOCAL, null);
		removeMessages(CMD_SUCCESS_REMOTE, null);
		removeMessages(CMD_RESULT, null);
		removeMessages(CMD_NO_RESULT, null);
		removeMessages(CMD_COMPRESS_IMG, null);
		removeMessages(CMD_SERVER_RECEIVED, null);
		removeMessages(CMD_NO_RESULT_BARCODE, null);
		removeMessages(CMD_NO_RESULT_LOCAL, null);
	}

	class Result {
		public String queryId;
		public String objId;
		public String objName;
		public String objMeta;
		public int engine;

		public Result(String queryId, String objId, String objName, String objMeta, int engine) {
			this.queryId = queryId;
			this.objId = objId;
			this.objMeta = objMeta;
			this.objName = objName;
			this.engine = engine;
		}

	}

	private class SearchWithImageRemote extends Thread {

		private File imgFile = null;
		private String extra = null;

		public SearchWithImageRemote(File imgFile, String extra) {
			this.imgFile = imgFile;
			this.extra = extra;
		}

		@Override
		public void run() {

			String qid;

			try {
				qid = iqRemote.DefineQueryId(imgFile, null, null, deviceId, true, null, null, null);
				onResultCallback.onQueryIdAssigned(qid, imgFile.getPath(), snap);
				remoteQueryMap.put(qid, onResultCallback);
				iqRemote.query(imgFile, deviceId, qid);
			} catch (IOException e) {
				onResultCallback.onNoResult(snap, e, imgFile);
				return;
			}
			// release the thread looking for server response
			synchronized (newIncomingRemoteMatchSemaphore) {
				newIncomingRemoteMatchSemaphore.notifyAll();
			}
		}

	}

	private class RemoteResultUpdateThread extends Thread {

		private IQEngineAdapter iqe;

		public RemoteResultUpdateThread(IQEngineAdapter iqe, String string) {
			super(string);
			this.iqe = iqe;
		}

		@Override
		public void run() {

			JSONObject results = null;

			while (iqeRunning.get() && updateThreadTimer.getAndDecrement() > 0) {
				synchronized (newIncomingRemoteMatchSemaphore) {
					try {
						newIncomingRemoteMatchSemaphore.wait(3000);
					} catch (InterruptedException e) {
						new RuntimeException(e);
					}
				}
				if (!iqeRunning.get()) {
					break;
				}

				try {
					String resultStr = null;
					resultStr = iqRemote.update(deviceId, true);
					if (resultStr == null) {
						continue;
					}
					try {
						JSONObject result = new JSONObject(resultStr);
						if (!result.has("data")) {
							continue;
						}
						results = result.getJSONObject("data");
					} catch (JSONException e) {
						Log.w("IQE", "Can't parse result", e);
						continue;
					}
				} catch (IOException e) {
					Log.w("IQE", "Server call failed", e);
					continue;
				}

				try {

					if (results.has("error")) {
						final int error = results.getInt("error");
						if (error != 0) {
							Log.e("IQE", "Server return error: " + error);
							continue;
						}
					}

					final JSONArray resultsList = results.getJSONArray("results");
					for (int i = 0, lim = resultsList.length(); i < lim; ++i) {

						final JSONObject resultObj = resultsList.getJSONObject(i);
						final String qid = resultObj.optString("qid");

						if (qid == null) {
							Log.e("IQE", "update result qid is null");
							continue;
						}

						final JSONObject qidData = resultObj.getJSONObject("qid_data");

						final String labels;
						final String meta;

						if (qidData.length() > 0) {
							labels = qidData.optString("labels", null);
							meta = qidData.optString("meta", null);
						} else {
							Log.w("Oh..", "NO_MATCH_FOUND_STR");
							labels = NO_MATCH_FOUND_STR;
							meta = null;
						}

						Message remoteResults = null;
						Result result;

						if (onResultCallback != null) {
							result = new Result(qid, null, labels, meta, remote);
							remoteResults = obtainMessage(CMD_SUCCESS_REMOTE, IQEngineAdapter.snap, 0, result);
							iqe.sendMessageAtFrontOfQueue(remoteResults);
						} else {

							onResultCallback = remoteQueryMap.get(qid);

							if (onResultCallback != null) {

								result = new Result(qid, null, labels, meta, remote);
								remoteResults = obtainMessage(CMD_SUCCESS_REMOTE, IQEngineAdapter.snap, 0, result);
								iqe.sendMessageAtFrontOfQueue(remoteResults);
							} else {
								Log.w("IQE", "OnResultCallback is null for qid: " + qid);
							}
						}
					}
				} catch (JSONException e) {
					Log.e("IQE", "JSON error", e);
				}
			}

		}
	}

	public void handleMessage(Message message) {

		switch (message.what) {
		case CMD_SUCCESS_REMOTE:

			// nb : "no match found" also considered as a successful remote
			// match

			Log.d("IQE", "-------------- REMOTE MATCH ---------------");
			sendMessageAtFrontOfQueue(obtainMessage(CMD_RESULT, message.arg1, 0, (Result) message.obj));
			break;

		/**
		 * CMD_RESULT message : obj = result arg1 = callType
		 */

		case CMD_RESULT:

			switch (message.arg1) {
			case (snap):
				Result resultSnap = (Result) message.obj;
				onResultCallback.onResult(resultSnap.queryId, resultSnap.objId, resultSnap.objName, resultSnap.objMeta,
						resultSnap.engine, message.arg1);
				break;
			}
			break;

		case CMD_NO_RESULT:

			Log.w("IQE", "-------------- NO MATCH FOUND ---------------");
			// send another a scan query unless a snap occurs
			if (message.arg1 == scan && isSnaping())
				break;

			// ugly hack to get rid of some bugs
			if (message.arg2 == 0)
				break;
			if (message.arg2 == 1) {
				if (secondCompteur == 0)
					secondCompteur++;
				else
					break;
			}

			Log.w("IQE", "-------------- NO MATCH FOUND ---------------");
			onResultCallback.onNoResult(message.arg1, null, imgFile);
			break;

		}
	}

	public boolean isSnaping() {
		return stopScanSearch.get();
	}

	public void performRemoteSearch(File imgFile, String searchRecordID) {
		this.imgFile = imgFile;
		new SearchWithImageRemote(imgFile, searchRecordID).start();
		this.start();
	}

}
